package com.start.workflow.core.manage;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.start.common.details.BaseUserDetails;
import com.start.common.entity.StartBaseUser;
import com.start.common.exceptions.StartBusException;
import com.start.common.tools.StartEnumTools;
import com.start.common.tools.UUIdTools;
import com.start.common.user.StartUserInfoLoadService;
import com.start.security.StartTokenTools;
import com.start.workflow.act.StartProcInstInst;
import com.start.workflow.act.StartProcTaskInst;
import com.start.workflow.common.enums.StartActConf;
import com.start.workflow.common.enums.StartActHandlerStatus;
import com.start.workflow.common.enums.StartActUserTaskConf;
import com.start.workflow.common.model.StartActConfigModel;
import com.start.workflow.core.StartActTools;
import com.start.workflow.core.dispatcher.StartGlobalActInstEventDispatcher;
import com.start.workflow.core.load.StartActCustomCandidateLoad;
import com.start.workflow.core.service.StartActBusinessService;
import com.start.workflow.core.service.StartActModelConfigService;
import com.start.workflow.entity.StartActBusinessEntity;
import com.start.workflow.entity.StartActModelConfig;
import com.start.workflow.vo.StartActNodeAction;
import com.start.workflow.vo.StartActiviti;
import com.start.workflow.vo.StartCustomActAuthUser;
import org.activiti.bpmn.constants.BpmnXMLConstants;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.ProcessInstanceQuery;
import org.activiti.engine.task.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 流程业务管理
 */
@Component
public class StartActInstManage {
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private HistoryService historyService;
    @Autowired
    private StartActBusinessService actBusinessService;

    @Autowired
    private StartActModelConfigService startActModelConfigService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private StartActUserTaskManage startActUserTaskManage;


    /**
     * 启动流程实例
     *
     * @param procDefKey
     * @param startActVo
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean startAct(String procDefKey, StartActiviti startActVo) {
        Deployment deployments = repositoryService.createDeploymentQuery().deploymentKey(procDefKey).latest().singleResult();
        if (deployments == null) throw new StartBusException("流程【" + procDefKey + "】未部署请先部署该流程");
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployments.getId()).latestVersion().singleResult();
        if (processDefinition == null) {
            throw new StartBusException("流程【" + procDefKey + "】的流程定义不存在请先发布流程");
        }
        BaseUserDetails currentUser = StartTokenTools.getCurrentUser();
        if (StringUtils.isNotBlank(startActVo.getUserId()) && !currentUser.isSuperUser()) {
            throw new StartBusException("您不具备代理申请权限");
        } else {
            startActVo.setUserId(currentUser.getUserId());
            startActVo.setStartUserType(currentUser.getStartUserType());
        }
        if (StringUtils.isAllBlank(startActVo.getStartUserType())) {
            throw new StartBusException("未指启动流程的用户体系类型");
        }

        Authentication.setAuthenticatedUserId(startActVo.getStartUserKey());

        StartActBusinessEntity businessEntity = createBusiness(startActVo, processDefinition);
        businessEntity.setProcDefCategory(deployments.getCategory());
        businessEntity.setId(UUIdTools.createSnowFakeId());
        StartGlobalActInstEventDispatcher.getInstance().onActInstanceCreateBefore(businessEntity);
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(procDefKey, businessEntity.getId(), startActVo.getBusinessVariables());
        if (processInstance == null || StringUtils.isAllBlank(processInstance.getProcessInstanceId())) {
            throw new StartBusException("流程启动失败");
        }
        businessEntity.setProcInstId(processInstance.getProcessInstanceId());
        handlerFormData(businessEntity, startActVo);
        actBusinessService.save(businessEntity);
        StartGlobalActInstEventDispatcher.getInstance().onActInstanceCreateAfter(businessEntity);
        handlerFirsIsCustomAuth(startActVo, businessEntity);
        return true;

    }

    /**
     * 处理流程表单数据
     *
     * @param businessEntity
     * @param startActVo
     */
    private void handlerFormData(StartActBusinessEntity businessEntity, StartActiviti startActVo) {
        JSONObject formData = startActVo.getBusinessFormData();
        String dataId = startActVo.getBusinessDataId();
        if (StringUtils.isAllBlank(dataId) && (formData == null || formData.isEmpty())) {
            throw new StartBusException("请提交表单数据");
        }
        if (StringUtils.isNotBlank(dataId)) {
            if (StringUtils.isAllBlank(startActVo.getBusinessFormId())) {
                throw new StartBusException("未找到有效的表单id");
            }
            return;
        }
        StartActModelConfig actModelConfig = startActModelConfigService.getActModelConfig(businessEntity.getProcDeployId(), businessEntity.getProcDefKey(), StartActConf.ACT_FORM_TABLE);
        if (actModelConfig == null) {
            throw new StartBusException("未找到有效的表单配置");
        }
        dataId = StartActFormManager.getInstance().saveProcData(actModelConfig, businessEntity.getProcInstId(), StartActHandlerStatus.AUTH_ING, startActVo.getBusinessFormData());
        businessEntity.setProcBusDataId(dataId);
        businessEntity.setProcBusFormId(actModelConfig.getConfVal());
    }

    private void handlerFirsIsCustomAuth(StartActiviti startActVo, StartActBusinessEntity businessEntity) {
        String procInstId = businessEntity.getProcInstId();
        if (startActVo.getNextTaskAuthUser() == null || startActVo.getNextTaskAuthUser().isEmpty()) {
            return;
        }
        List<Task> runTasks = taskService.createTaskQuery().processInstanceId(procInstId).list();
        if (runTasks.isEmpty()) {
            return;
        }
        LambdaQueryWrapper<StartActModelConfig> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(StartActConfigModel::getProcDefKey);
        queryWrapper.eq(StartActConfigModel::getProcDeployId, businessEntity.getProcDeployId());
        queryWrapper.eq(StartActConfigModel::getActType, StartActUserTaskConf.CANDIDATE_USER.getActType());
        queryWrapper.eq(StartActConfigModel::getConfType, StartActUserTaskConf.CANDIDATE_USER.getValue());
        queryWrapper.eq(StartActConfigModel::getConfKey, StartActCustomCandidateLoad.CUSTOM_AUTH_CANDIDATE);
        queryWrapper.in(StartActConfigModel::getProcDefKey, runTasks.stream().map(TaskInfo::getTaskDefinitionKey).collect(Collectors.toList()));
        List<String> confKeys = startActModelConfigService.list(queryWrapper).stream().map(StartActConfigModel::getProcDefKey).collect(Collectors.toList());
        if (confKeys.isEmpty()) return;
        List<StartCustomActAuthUser> nextTaskAuthUser = startActVo.getNextTaskAuthUser();
        for (Task task : runTasks) {
            if (!confKeys.contains(task.getTaskDefinitionKey())) {
                continue;
            }
            for (StartCustomActAuthUser user : nextTaskAuthUser) {
                startActUserTaskManage.addUserTaskCandidateUser(task.getId(), user);
            }
        }

    }

    /**
     * 创建流程业务
     *
     * @param startActVo
     * @param processDefinition
     * @return
     */
    private StartActBusinessEntity createBusiness(StartActiviti startActVo, ProcessDefinition processDefinition) {
        StartActBusinessEntity entity = new StartActBusinessEntity();
        entity.setProcDefName(processDefinition.getName());
        entity.setProcDefKey(processDefinition.getKey());
        entity.setProcDefCategory(processDefinition.getCategory());
        entity.setProcDefVersion(processDefinition.getVersion());
        entity.setProcDeployId(processDefinition.getDeploymentId());
        entity.setProcDefId(processDefinition.getId());

        entity.setProcStartUserKey(startActVo.getStartUserKey());
        entity.setProcStartUserId(startActVo.getUserId());
        entity.setProcStartUserType(startActVo.getStartUserType());

        entity.setProcBusFormId(startActVo.getBusinessFormId());
        entity.setProcBusDataId(startActVo.getBusinessDataId());
        entity.setCreateBy(startActVo.getUserId());
        entity.setProcStartTime(LocalDateTime.now());
        entity.setProcHandlerState(StartActHandlerStatus.AUTH_ING.getValue());
        return entity;
    }


    /**
     * 删除流程申请
     *
     * @param actBusinessId
     * @param reason
     */
    public void cancelApply(String actBusinessId, String reason) {
        StartActBusinessEntity businessEntity = actBusinessService.getById(actBusinessId);
        if (businessEntity == null) {
            return;
        }
        String procInstId = businessEntity.getProcInstId();
        StartActHandlerStatus startActHandlerStatus = StartEnumTools.findEnumByValue(StartActHandlerStatus.class, businessEntity.getProcHandlerState());
        if (startActHandlerStatus == null) {
            throw new StartBusException("未知流程状态，请联系管理员");
        }
        if (StringUtils.isEmpty(reason)) {
            throw new StartBusException("请输入原因");
        }
        switch (startActHandlerStatus) {
            case agree:
                throw new StartBusException("该审批流程已审批通过无法取消");
            case UN_START:
                throw new StartBusException("该审批流程未提交或已被撤销");
        }
        StartGlobalActInstEventDispatcher.getInstance().onActInstanceCancelBefore(businessEntity);
        List<ProcessInstance> processInstances = runtimeService.createProcessInstanceQuery().processInstanceId(procInstId).list();
        if (!processInstances.isEmpty()) {
            runtimeService.deleteProcessInstance(actBusinessId, reason);
        }
        List<HistoricProcessInstance> historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).list();
        if (!historicProcessInstanceList.isEmpty()) {
            historyService.deleteHistoricProcessInstance(procInstId);
        }
        actBusinessService.updateById(businessEntity);
        StartGlobalActInstEventDispatcher.getInstance().onActInstanceCancelAfter(businessEntity);
    }


    /**
     * 获取流程实例详情
     *
     * @param actBusinessId
     * @param loadFormOption
     */
    public StartProcInstInst getActInsDetail(String actBusinessId, boolean loadFormOption) {
        StartActBusinessEntity businessEntity = actBusinessService.getById(actBusinessId);
        return loadActInst(businessEntity, loadFormOption);

    }

    public StartProcInstInst loadActInst(StartActBusinessEntity businessEntity, boolean loadFormOption) {
        if (businessEntity == null) {
            throw new StartBusException("未知流程");
        }
        StartProcInstInst procInstDetail = loadActInstDetail(businessEntity, loadFormOption);

        BpmnModel bpmnModel = repositoryService.getBpmnModel(procInstDetail.getProcDefId());
        byte[] bytes = new BpmnXMLConverter().convertToXML(bpmnModel);
        String bpmnXml = new String(bytes, StandardCharsets.UTF_8);
        procInstDetail.setBpmnXml(bpmnXml);
        List<StartProcTaskInst> nodeDetails = loadActNodes(businessEntity, bpmnModel, loadFormOption);
        procInstDetail.setProcTasks(nodeDetails);
        return procInstDetail;
    }

    /**
     * 加载流程实例详情
     *
     * @param actBusinessEntity
     * @param loadFormOption
     * @return
     */
    private StartProcInstInst loadActInstDetail(StartActBusinessEntity actBusinessEntity, boolean loadFormOption) {
        StartProcInstInst procInstDetail = new StartProcInstInst(actBusinessEntity);
        StartActHandlerStatus handlerStatus = StartEnumTools.findEnumByValue(StartActHandlerStatus.class, actBusinessEntity.getProcHandlerState());
        procInstDetail.setProcState(handlerStatus);
        if (handlerStatus == null) {
            throw new StartBusException("流程状态异常");
        }
        if (handlerStatus == StartActHandlerStatus.UN_START) {
            return procInstDetail;
        }
        //加载表单数据
        StartActModelConfig formConfig = startActModelConfigService.getActModelConfig(actBusinessEntity.getProcDeployId(), actBusinessEntity.getProcDefKey(), StartActConf.ACT_FORM_TABLE);
        procInstDetail.setProcFormConfig(formConfig);
        if (loadFormOption) {
            JSONObject jsonObject = StartActFormManager.loadFormOption(formConfig);
            procInstDetail.setFormModelOption(jsonObject);
        }
        JSONObject procFormData = StartActFormManager.getInstance().getProcFormData(formConfig, actBusinessEntity.getProcInstId());
        procInstDetail.setProcFormData(procFormData);
        StartBaseUser baseUser = StartUserInfoLoadService.getInstance().getUserById(actBusinessEntity.getProcStartUserType(), actBusinessEntity.getProcStartUserId());
        baseUser.setUserPwd(null);
        procInstDetail.setStartUser(baseUser);

        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(actBusinessEntity.getProcInstId()).singleResult();
        String procDefId = historicProcessInstance.getProcessDefinitionId();
        String procDeploymentId = historicProcessInstance.getDeploymentId();
        procInstDetail.setProcDeployId(procDeploymentId);
        procInstDetail.setProcDefId(procDefId);

        return procInstDetail;
    }

    /**
     * 加载流程节点
     *
     * @param actBusinessEntity
     * @param bpmnModel
     * @param loadFormOption
     * @return
     */
    private List<StartProcTaskInst> loadActNodes(StartActBusinessEntity actBusinessEntity, BpmnModel bpmnModel, boolean loadFormOption) {
        Map<String, Map<String, StartActModelConfig>> taskConfigMap = startActModelConfigService.getActModelConfigMap(actBusinessEntity.getProcDeployId(), StartActUserTaskConf.CANDIDATE_USER);
        List<StartProcTaskInst> taskHistory = loadHistoryTask(actBusinessEntity, taskConfigMap, loadFormOption);
        /**
         * 加载真正执行的任务 以及待执行的任务
         */
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(actBusinessEntity.getProcInstId()).orderByTaskCreateTime().asc().list();
        List<String> elementTypes = new ArrayList<String>();
        elementTypes.add(BpmnXMLConstants.ELEMENT_TASK_USER);
        for (Task task : tasks) {
            StartProcTaskInst taskInst = new StartProcTaskInst(task);
            List<IdentityLink> linkList = taskService.getIdentityLinksForTask(task.getId());
            List<String> userKeys = linkList.stream().filter(e -> e.getType().equals("candidate") && StringUtils.isNotBlank(e.getUserId())).distinct().map(IdentityLink::getUserId).collect(Collectors.toList());
            List<StartBaseUser> candidateUser = StartUserInfoLoadService.getInstance().getUsers(userKeys);
            taskInst.setCandidateUsers(candidateUser);
            taskHistory.add(taskInst);
            taskInst.setHandlerState(StartActHandlerStatus.AUTH_ING.getValue());
            taskInst.setCreateTime(DateUtil.toLocalDateTime(task.getCreateTime()));
            StartActTools.bpmnModelFindElement(bpmnModel, null, task.getTaskDefinitionKey(), elementTypes, null, (level, elementType, flowElement) -> {
                if (level < 1) return true;
                UserTask userTask = (UserTask) flowElement;
                Map<String, StartActModelConfig> tempMap = taskConfigMap.getOrDefault(userTask.getId(), null);
                if (tempMap == null || tempMap.isEmpty() || !tempMap.containsKey(StartActUserTaskConf.CANDIDATE_USER.getValue()))
                    return false;
                StartActModelConfig config = tempMap.get(StartActUserTaskConf.CANDIDATE_USER.getValue());
                StartProcTaskInst nextInst = new StartProcTaskInst(userTask);
                nextInst.setCandidateUserConfig(config);
                nextInst.setHandlerState(StartActHandlerStatus.WAIT_AUTH_TASK.getValue());
                taskHistory.add(nextInst);
                return false;
            });
            Map<String, StartActModelConfig> configMap = taskConfigMap.getOrDefault(task.getTaskDefinitionKey(), null);
            if (configMap == null || configMap.isEmpty()) {
                continue;
            }
            StartActModelConfig startActModelConfig = configMap.getOrDefault(StartActUserTaskConf.FORM_TABLE.getValue(), null);
            taskInst.setTaskFormConfig(startActModelConfig);
            if (startActModelConfig != null && loadFormOption) {
                JSONObject jsonObject = StartActFormManager.loadFormOption(startActModelConfig);
                taskInst.setNodeFormOption(jsonObject);
            }

        }
        return taskHistory;
    }


    /**
     * 加载流程审批历史记录
     *
     * @param actBusiness
     * @param taskConfigMap
     * @param loadFormOption
     * @return
     */
    private List<StartProcTaskInst> loadHistoryTask(StartActBusinessEntity actBusiness, Map<String, Map<String, StartActModelConfig>> taskConfigMap, boolean loadFormOption) {
        List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().activityType(BpmnXMLConstants.ELEMENT_TASK_USER).processInstanceId(actBusiness.getProcInstId()).finished().list();
        List<StartProcTaskInst> result = new ArrayList<>();
        if (historicActivityInstances.isEmpty()) {
            return result;
        }
        Map<String, List<StartActNodeAction>> commentMap = taskService.getProcessInstanceComments(actBusiness.getProcInstId(), BpmnXMLConstants.ELEMENT_TASK_USER).stream().collect(
                Collectors.groupingBy(Comment::getTaskId, Collectors.mapping(e -> JSONObject.parseObject(e.getFullMessage(), StartActNodeAction.class), Collectors.toList())));
        List<String> assigneeUserKeys = historicActivityInstances.stream().map(HistoricActivityInstance::getAssignee).filter(StringUtils::isNotBlank).collect(Collectors.toList());
        Map<String, StartBaseUser> userMap = StartUserInfoLoadService.getInstance().getUsersGroupByUserKey(assigneeUserKeys);
        for (HistoricActivityInstance historyActivityInstance : historicActivityInstances) {
            String assignee = historyActivityInstance.getAssignee();
            StartProcTaskInst taskInst = new StartProcTaskInst(historyActivityInstance);
            taskInst.setHandlerUser(userMap.get(assignee));
            List<StartActNodeAction> nodeActions = commentMap.getOrDefault(historyActivityInstance.getTaskId(), new ArrayList<StartActNodeAction>());
            taskInst.initComment(nodeActions);
            Map<String, StartActModelConfig> configMap = taskConfigMap.getOrDefault(historyActivityInstance.getActivityId(), new HashMap<>());
            StartActModelConfig taskFormConfig = configMap.getOrDefault(StartActUserTaskConf.FORM_TABLE.getValue(), null);
            taskInst.setTaskFormConfig(taskFormConfig);
            result.add(taskInst);
            if (taskFormConfig == null) {
                continue;
            }
            if (loadFormOption) {
                JSONObject jsonObject = StartActFormManager.loadFormOption(taskFormConfig);
                taskInst.setNodeFormOption(jsonObject);
            }
            JSONObject formData = StartActFormManager.getInstance().geProcTaskData(taskFormConfig, actBusiness.getProcInstId(), historyActivityInstance.getTaskId());
            taskInst.setNodeFormData(formData);
        }
        return result;
    }


    /**
     * 删除流程实例
     *
     * @param actBusinessId
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteActIns(String actBusinessId) {
        if (StringUtils.isAllBlank(actBusinessId)) return;
        StartActBusinessEntity businessEntity = actBusinessService.getById(actBusinessId);
        if (businessEntity == null) return;
        Map<String, Map<String, Map<String, StartActModelConfig>>> actModelConfigMap = startActModelConfigService.getActModelConfigMap(businessEntity.getProcDeployId());
        Map<String, Map<String, StartActModelConfig>> actConfigMap = actModelConfigMap.getOrDefault(StartActConf.ACT_FORM_TABLE.getActType(), new HashMap<>());
        Map<String, Map<String, StartActModelConfig>> taskConfigMap = actModelConfigMap.getOrDefault(StartActUserTaskConf.FORM_TABLE.getActType(), new HashMap<>());
        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery().processInstanceId(businessEntity.getProcInstId());
        if (processInstanceQuery.count() > 0) {
            List<Task> runTaskList = taskService.createTaskQuery().processInstanceId(businessEntity.getProcInstId()).list();
            for (Task task : runTaskList) {

                Map<String, StartActModelConfig> taskMap = taskConfigMap.getOrDefault(task.getTaskDefinitionKey(), new HashMap<>());
                if (!taskMap.containsKey(StartActUserTaskConf.FORM_TABLE.getValue())) {
                    continue;
                }
                StartActModelConfig startActModelConfig = taskMap.get(StartActUserTaskConf.FORM_TABLE.getValue());
                StartActFormManager.getInstance().delProcTaskDataByDefKey(startActModelConfig, businessEntity.getProcInstId(), task.getTaskDefinitionKey());
            }
            runtimeService.deleteProcessInstance(businessEntity.getProcInstId(), "");
        }

        List<HistoricActivityInstance> historyTaskList = historyService.createHistoricActivityInstanceQuery().processInstanceId(businessEntity.getProcInstId()).activityType(BpmnXMLConstants.ELEMENT_TASK_USER).orderByHistoricActivityInstanceStartTime().desc().list();
        for (HistoricActivityInstance task : historyTaskList) {
            Map<String, StartActModelConfig> taskMap = taskConfigMap.getOrDefault(task.getActivityId(), new HashMap<>());
            if (!taskMap.containsKey(StartActUserTaskConf.FORM_TABLE.getValue())) {
                continue;
            }
            StartActModelConfig startActModelConfig = taskMap.get(StartActUserTaskConf.FORM_TABLE.getValue());
            StartActFormManager.getInstance().delProcTaskDataByDefKey(startActModelConfig, businessEntity.getProcInstId(), task.getActivityId());
        }
        Map<String, StartActModelConfig> actConfig = actConfigMap.getOrDefault(businessEntity.getProcDefKey(), new HashMap<>());

        if (actConfig.containsKey(StartActConf.ACT_FORM_TABLE.getValue())) {
            StartActModelConfig startActModelConfig = actConfig.get(StartActConf.ACT_FORM_TABLE.getValue());
            StartActFormManager.getInstance().delProcFormData(startActModelConfig, businessEntity.getProcInstId());
        }
        historyService.deleteHistoricProcessInstance(businessEntity.getProcInstId());
        actBusinessService.removeById(actBusinessId);
    }

    /**
     * 加载待办流程详情
     *
     * @param taskId
     * @param loadFormOption
     * @return
     */
    public Object getWaitAuthTaskDetail(String taskId, boolean loadFormOption) {
        TaskQuery taskQuery = taskService.createTaskQuery().taskId(taskId);
        if (taskQuery.count() < 1) {
            throw new StartBusException("该任务已被其他人处理");
        }
        Task task = taskQuery.singleResult();
        String processInstanceId = task.getProcessInstanceId();
        StartActBusinessEntity businessEntity = actBusinessService.getOneByProcessInstanceId(processInstanceId);
        return loadActInst(businessEntity, loadFormOption);
    }
}
